# -*- coding: utf-8 -*-

# this file is applicated for Hardware PicoLin Tool

import serial, time

from ctypes import Structure, c_uint8, c_uint16, c_uint32, sizeof
# from general_lin import General_LIN_Message, GeneralLin
import serial.tools
import serial.tools.list_ports
import serial.tools.list_ports_common

"""
 tx: t [type] [id] [dlc] xx xx xx xx xx xx xx xx \r \n
 example: t01180102030405060708\r\n
 rx: r [type] [id] [dlc] \r \n
 example: r0208\r\n
"""

hex_2_ascii_map = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']
    
class GeneralLin():
    def __init__(self) -> None:
        pass
    
    @staticmethod
    def getDevList():
        pass
    
    def setDev(self, name):
        pass

    def openDev(self):
        pass

    def closeDev(self):
        pass

    def sendFrame(self):
        pass

    def sendHeader(self):
        pass

class _picolin_cmd_message(Structure):
    _fields_ = [('id', c_uint8),
                ('dlc', c_uint8),
                ('bytes', c_uint8*8),
                ('delay', c_uint8),
                ('unused',c_uint8)]
    
class _picolin_rsp_message(Structure):
    _fields_ = [('dir', c_uint8),
                ('event', c_uint8),
                ('id', c_uint8),
                ('dlc', c_uint8),
                ('bytes', c_uint8*8),
                ('checksum', c_uint8),
                ('unused', c_uint8*3),
                ('timestamp', c_uint32)]
    
class PicoLinCom(GeneralLin):
    def __init__(self, port=None, baudrate=256000):
        super().__init__()
        self.initiazed = False
        self.serial = None
        if port is not None:
            self.serial = serial.Serial(port=port, baudrate=baudrate, timeout=0.01)
            self.initiazed = True
        self.message_str = ""

    def setDev(self, name: str):
        self.serial.name = name

    @staticmethod
    def getDevList():
        com = []
        ports = list(serial.tools.list_ports.comports())
        for port in ports:
            if port.pid == 20535 and port.vid == 7504:
                com.append(port.name)
        return com
    
    def openDev(self):
        self.serial.open()

    def closeDev(self):
        self.serial.close()

    @staticmethod
    def hex2Ascii(hex, len):
        s0 = ""
        if len == 1:
            b = hex & 0x0f
            s0 += hex_2_ascii_map[b]
        elif len == 2:
            b = (hex & 0xf0) >> 4
            s0 += hex_2_ascii_map[b]  
            b = hex & 0x0f
            s0 += hex_2_ascii_map[b]
        elif len == 4:
            b = (hex & 0xf000) >> 12
            s0 += hex_2_ascii_map[b] 
            b = (hex & 0x0f00) >> 8
            s0 += hex_2_ascii_map[b] 
            b = (hex & 0x00f0) >> 4
            s0 += hex_2_ascii_map[b] 
            b = hex & 0x0f
            s0 += hex_2_ascii_map[b] 
        return s0
    
    @staticmethod
    def ascii2hex(ascii, len):
        b = 0x0
        if len == 1:
            if ascii[0] >= 0x41 and ascii[0] <= 0x46:
                b = ascii[0] - 0x41 + 0xA
            elif ascii[0] >= 0x61 and ascii[0] <= 0x66:
                b = ascii[0] - 0x61 + 0xA
            elif ascii[0] >= 0x30 and ascii[0] <= 0x39:
                b = ascii[0] - 0x30  
        elif len == 2:
            if ascii[0] >= 0x41 and ascii[0]  <= 0x46:
                b = ascii[0] - 0x41 + 0xA
            elif ascii[0]  >= 0x61 and ascii[0]  <= 0x66:
                b = ascii[0] - 0x61 + 0xA
            elif ascii[0]  >= 0x30 and ascii[0]  <= 0x39:
                b = ascii[0]  - 0x30    
            b = b << 4
            if ascii[1] >= 0x41 and ascii[1] <= 0x46:
                b += (ascii[1] - 0x41 + 0xA)
            elif ascii[1] >= 0x61 and ascii[1] <= 0x66:
                b += (ascii[1] - 0x61 + 0xA)
            elif ascii[1] >= 0x30 and ascii[1] <= 0x39:
                b += (ascii[1] - 0x30)
        return b
    
    @staticmethod
    def paserEventId(event) -> str:
        if event == 0:
            return "Normal"
        elif event == 4:
            return "Slave Not Response"
        else:
            return "Unkown"
        
    def checkCommandResponse(self) -> int:
        try:
            str = self.serial.readline()
            if len(str) >= 4:
                if str[0] == b"O"[0] and str[1] == b"K"[0]:
                    return 0
                else:
                    self.serial.flush()
                    return -1
            else:
                self.serial.flush()
                return -1
        except:
            return -2  
    
    def sendFrame(self, id, dlc, data):
        if self.initiazed is True:

            strAscii = "t0"
            strAscii += PicoLinCom.hex2Ascii(id, 2)
            strAscii += PicoLinCom.hex2Ascii(dlc, 1)
            for i in range(dlc):
                strAscii += PicoLinCom.hex2Ascii(data[i], 2)
            strAscii += "\r\n"
            abc = bytes(strAscii, 'utf-8')

            try:
                ret = self.serial.write(abc)
            except:
                return -1
            
            try:
                str = self.serial.readline()
                if len(str) >= (len(abc) + 10):
                    buf = bytearray(sizeof(_picolin_rsp_message)) 
                    # type(str) is bytes 'char', need to convert to hex
                    if str[0] == 0x74:
                        buf[0] = 0
                        buf[1] = PicoLinCom.ascii2hex(str[1:2], 1)
                        buf[2] = PicoLinCom.ascii2hex(str[2:4], 2)
                        buf[3] = PicoLinCom.ascii2hex(str[4:5], 1) 
                        index = 4
                        for i in range(5, 7 + dlc*2, 2):
                            buf[index] = PicoLinCom.ascii2hex(str[i:i+2], 2)
                            index += 1
                        index += 3
                        for i in range(7 + dlc*2, 15 + dlc*2, 2):
                            buf[index] = PicoLinCom.ascii2hex(str[i:i+2], 2)
                            index += 1   
                        msg = _picolin_rsp_message.from_buffer(buf)
                        return msg
                    else:
                        self.serial.flush()
                        return -2
                else:
                    self.serial.flush()
                    return -2
            except:
                return -3
            
    def sendHeader(self, id, dlc):
        if self.initiazed is True:
            strAscii = "r0"
            strAscii += PicoLinCom.hex2Ascii(id, 2)
            strAscii += PicoLinCom.hex2Ascii(dlc, 1)      
            strAscii += "\r\n"
            abc = bytes(strAscii, 'utf-8')

            try:
                ret = self.serial.write(abc)
            except:
                return -1

            try:
                str = self.serial.readline()
                if len(str) >= (15 + dlc*2):
                    buf = bytearray(sizeof(_picolin_rsp_message)) 
                    # return ascii, need to convert to hex
                    if str[0] == 0x72:
                        buf[0] = 1
                        buf[1] = PicoLinCom.ascii2hex(str[1:2], 1)
                        buf[2] = PicoLinCom.ascii2hex(str[2:4], 2)
                        buf[3] = PicoLinCom.ascii2hex(str[4:5], 1) 
                        index = 4
                        for i in range(5, 7 + dlc*2, 2):
                            buf[index] = PicoLinCom.ascii2hex(str[i:i+2], 2)
                            index += 1
                        index += 3
                        for i in range(7 + dlc*2, 15 + dlc*2, 2):
                            buf[index] = PicoLinCom.ascii2hex(str[i:i+2], 2)
                            index += 1   
                        msg = _picolin_rsp_message.from_buffer(buf)
                        return msg
                    else:
                        self.serial.flush()
                        return -2
                elif len(str) >= (17):
                    buf = bytearray(sizeof(_picolin_rsp_message)) 
                    # slvae not resonse
                    if str[0] == 0x72:
                        buf[0] = 1
                        buf[1] = PicoLinCom.ascii2hex(str[1:2], 1)
                        buf[2] = PicoLinCom.ascii2hex(str[2:4], 2)
                        buf[3] = PicoLinCom.ascii2hex(str[4:5], 1)
                        dlc = buf[3]
                        index = 16
                        for i in range(7 + dlc*2, 15 + dlc*2, 2):
                            buf[index] = PicoLinCom.ascii2hex(str[i:i+2], 2)
                            index += 1   
                        msg = _picolin_rsp_message.from_buffer(buf)
                        return msg
                    else:
                        self.serial.flush()
                        return -2                    
                else:
                    self.serial.flush()
                    return -2
            except:
                return -3      

    def addFrameToSchedule(self, id, dlc, data, delay):
        if self.initiazed is True:

            strAscii = "t1"
            strAscii += PicoLinCom.hex2Ascii(id, 2)
            strAscii += PicoLinCom.hex2Ascii(dlc, 1)
            for i in range(dlc):
                strAscii += PicoLinCom.hex2Ascii(data[i], 2)
            strAscii += PicoLinCom.hex2Ascii(delay, 4)
            strAscii += "\r\n"
            abc = bytes(strAscii, 'utf-8')

            try:
                ret = self.serial.write(abc)
            except:
                return -1
            
            try:
                str = self.serial.readline()
                if len(str) >= 2:
                    if str[0] == b"O"[0] and str[1] == b"K"[0]:
                        return 0
                    else:
                        self.serial.flush()
                        return -1
                else:
                    self.serial.flush()
                    return -1
            except:
                return -2      

    def addHeaderToSchedule(self, id, dlc, delay):
        if self.initiazed is True:

            strAscii = "r1"
            strAscii += PicoLinCom.hex2Ascii(id, 2)
            strAscii += PicoLinCom.hex2Ascii(dlc, 1)
            strAscii += PicoLinCom.hex2Ascii(delay, 4)
            strAscii += "\r\n"
            abc = bytes(strAscii, 'utf-8')

            try:
                ret = self.serial.write(abc)
            except:
                return -1
            
            try:
                str = self.serial.readline()
                if len(str) >= 4:
                    if str[0] == b"O"[0] and str[1] == b"K"[0]:
                        return 0
                    else:
                        self.serial.flush()
                        return -1
                else:
                    self.serial.flush()
                    return -1
            except:
                return -2      
            
    def startSchedule(self):
        if self.initiazed is True:
            strAscii = "L1\r\n"
            abc = bytes(strAscii, 'utf-8')
            try:
                ret = self.serial.write(abc)
                return 0
            except:
                return -1

    def stopSchedule(self):
        if self.initiazed is True:
            strAscii = "L0\r\n"
            abc = bytes(strAscii, 'utf-8')
            try:
                ret = self.serial.write(abc)
            except:
                return -1        

            return self.checkCommandResponse()
    
    def getMessages(self):
        if self.initiazed is True:
            try:
                str = self.serial.readline()
                self.message_str = str.decode('ascii')
            except:
                return None
            
            if len(self.message_str) >= 33:
                if self.message_str[31] == "\r" and self.message_str[32] == "\n":
                    if self.message_str[0] == "t":
                        buf = bytearray(sizeof(_picolin_rsp_message)) 
                        # dir
                        buf[0] = 0x0
                        # event
                        buf[1:2] = bytearray.fromhex("0" + self.message_str[1])
                        # id
                        buf[2:3] = bytearray.fromhex(self.message_str[2:4])
                        # dlc
                        buf[3:4] = bytearray.fromhex("0" + self.message_str[4:5])
                        # data+checksum
                        buf[4:13] = bytearray.fromhex(self.message_str[5:23])
                        # timestamp
                        buf[16:20] = bytearray.fromhex(self.message_str[23:31])
                        # cut ..
                        # self.message_str = self.message_str[33:]
 
                        msg = _picolin_rsp_message.from_buffer(buf)
                        return msg
                    elif self.message_str[0] == "r":
                        buf = bytearray(sizeof(_picolin_rsp_message)) 
                        # dir
                        buf[0] = 0x1
                        # event
                        buf[1:2] = bytearray.fromhex("0" + self.message_str[1])
                        # id
                        buf[2:3] = bytearray.fromhex(self.message_str[2:4])
                        # dlc
                        buf[3:4] = bytearray.fromhex("0" + self.message_str[4:5])
                        # data+checksum
                        buf[4:13] = bytearray.fromhex(self.message_str[5:23])
                        # timestamp
                        buf[16:20] = bytearray.fromhex(self.message_str[23:31])
                        # cut ..
                        # self.message_str = self.message_str[33:]
 
                        msg = _picolin_rsp_message.from_buffer(buf)
                        # print("1")
                        return msg
                    else:
                        return None
                else:
                    return None 
            elif len(self.message_str) >= 19:
                if self.message_str[17] == "\r" and self.message_str[18] == "\n":
                    if self.message_str[0] == "r":
                        buf = bytearray(sizeof(_picolin_rsp_message)) 
                        # dir
                        buf[0] = 0x1
                        # event
                        buf[1:2] = bytearray.fromhex("0" + self.message_str[1])
                        # id
                        buf[2:3] = bytearray.fromhex(self.message_str[2:4])

                        # timestamp
                        buf[16:20] = bytearray.fromhex(self.message_str[9:17])
                        # cut ..
                        # self.message_str = self.message_str[33:]
 
                        msg = _picolin_rsp_message.from_buffer(buf)
                        # print("1")
                        return msg
                    else:
                        return None  
                else:
                    return None                 
            else:
                return None
            

    def setMode(self, mode) -> int:
        # "s0\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "s"
        strAscii += PicoLinCom.hex2Ascii(mode, 1)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1
        
        return self.checkCommandResponse()       

    def setBreakLength(self, t_bit) -> int:
        # "p20b\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "p2"
        strAscii += PicoLinCom.hex2Ascii(t_bit, 2)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1
        
        return self.checkCommandResponse() 
        
    def setDelimeterLength(self, t_bit) -> int:
        # "p30a\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "p3"
        strAscii += PicoLinCom.hex2Ascii(t_bit, 2)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1  
        
        return self.checkCommandResponse() 

    def setHeaderLength(self, t_bit) -> int:
        # "p401\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "p4"
        strAscii += PicoLinCom.hex2Ascii(t_bit, 2)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1  
        
        return self.checkCommandResponse()        

    def setTimeStampMode(self, mode = 0) -> int: 
        # "p001\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "p0"
        strAscii += PicoLinCom.hex2Ascii(mode, 2)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1  
        
        return self.checkCommandResponse()       

    def setBaudrateUpOrDown(self, mode = 0) -> int: 
        # "p101\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "p1"
        strAscii += PicoLinCom.hex2Ascii(mode, 2)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1  
        
        return self.checkCommandResponse() 

    def setIncompleteHeaderFlag(self, flag = 0) -> int:
        # "p501\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "p5"
        strAscii += PicoLinCom.hex2Ascii(flag, 2)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1  
        
        return self.checkCommandResponse()               

    def setErrorBitFlag(self, flag = 0) -> int:
        # "p601\r\n"
        if self.initiazed is False:
            return -1
        
        strAscii = "p6"
        strAscii += PicoLinCom.hex2Ascii(flag, 2)
        strAscii += "\r\n"
        abc = bytes(strAscii, 'utf-8')

        try:
            ret = self.serial.write(abc)
        except:
            return -1  
        
        return self.checkCommandResponse() 


